pimpas.HTTPRequest = Class.create( );

pimpas.Extends(pimpas.HTTPRequest.prototype, pimpas.Object);
pimpas.HTTPRequest.Listener = pimpas.Listener;

pimpas.HTTPRequest.ListenerSupport = Class.create();
pimpas.Extends(pimpas.HTTPRequest.ListenerSupport.prototype, pimpas.ListenerSupport.prototype);
pimpas.Extends(pimpas.HTTPRequest.ListenerSupport.prototype, {
	callListeners: function() {
		var evt = arguments[0];
		var ar = new Array();
		ar.push(this);
		
		this.log.info("Calling HTTPRequest listeners for event ["+evt+"]");
		
		for(var i=0;i<arguments.length;i++) ar.push(arguments[i]);
		
 		for(var i=0;i<this.listeners.length;i++)
			this.listeners[i].callBack.apply(this.listeners[i], ar);
			
		for(i=0;i<pimpas.HTTPRequest.globalListeners.length;i++)
			pimpas.HTTPRequest.globalListeners[i].callBack.apply(pimpas.HTTPRequest.globalListeners[i], ar);
		
		return evt;
	}
   }
);

pimpas.Extends(pimpas.HTTPRequest.prototype, pimpas.HTTPRequest.ListenerSupport.prototype);

pimpas.Extends(pimpas.HTTPRequest.prototype, 
 {
	url: null,
	setUrl: function(t) {
		this.url = t;	
	},
	getUrl: function() {
		return this.url;	
	},
	pooled: false,
	isPooled: function() {
		return this.pooled;
	},
	setPooled: function(p) {
		this.pooled = p;
	},
	async: null,
	setAsync: function(a) {
		this.async = a;
	},
	getAsync: function() {
		return this.async;
	},
	status: null,
	queueMode: null,
	listeners: null,
	transport: null,
	requestMethod: null,
	formString: null,
	setRequestMethod: function(t) {
		this.requestMethod = t;	
	},
	getRequestMethod: function() {
		return this.requestMethod;
	},
	userName: null,
	passWord: null,
	parameters: null,
	maxTime: null,
	getMaxTime: function( ) {
		return this.maxTime;
	},
	setMaxTime: function(t) {
		this.maxTime = t;
	},
	timer: null,
	lastCall: null,
	addParameter: function(k,v) {
		this.parameters.push([k,v]);
	},
	headers: null,
	processLife: function( ) {
		if(pimpas.HTTPRequest.isFinished(this.status))
			// just go away
			return;
		// check lastCall
		var t = new Date().getTime();
		if( (t - this.lastCall) > this.maxTime) {
			this.log.log(pimpas.Log.INFO, "Object "+this.id+" is possible dead. Killing Request...");
			this.abort( );
			return;
		}
		
		this.log.info("HttpRequest Object with ID ["+this.id+"] its not dead! Give it another chance.");
		this.timer.go( );
	},
	cleanUp: function( ) {
		this.parameters = new Array();
		this.clearListeners( );
		this.async = true;
		this.url = null;
		this.transport = null;
		this.userName = null;
		this.passWord = null;
		this.headers = new Array();
		this.log = new pimpas.Log();
		this.maxTime = pimpas.HTTPRequest.defaultTime;
		this.pooled = false;
		this.formString = null;
		this.lastCall = null;
		this.queueMode = false;
		this.status = this.callListeners(pimpas.HTTPRequest.events.unInitialized);
	},
	initialize: function(  ) {
		this.id = pimpas.globalID++;
		this.cleanUp( );
		this.requestMethod = pimpas.HTTPRequest.methods.GET;
		this.log.log(pimpas.Log.DEBUG, 'Creating Object with ID '+this.id);
		pimpas.HTTPRequest.requests.push(this);
	},
	initTransport: function( ) {
		this.timer = new pimpas.Timer(this.maxTime, this.processLife, this);
   		this.transport = pimpas.HTTPRequest.createTransport();
		this.status = this.callListeners(pimpas.HTTPRequest.events.onCreate);
		this.log.log(pimpas.Log.DEBUG, 'Transport Created for Object '+this.id);
	},
	sendForm: function( obj, filter ) {
		this.formString = pimpas.DOM.serializeForm( obj, filter );
		var frm = pimpas.DOM.getParentForm( obj );
		this.setUrl( frm.action );
		this.setRequestMethod( frm.method==pimpas.HTTPRequest.methods.POST ? pimpas.HTTPRequest.methods.POST : pimpas.HTTPRequest.methods.GET);
	},
	processParameters: function( ) {
		var parameters = new Array();
		var el = null;
		while(this.parameters.length > 0) {
			el = this.parameters.shift();
			parameters.push(el.join('='));
		}
		
		var pString = (this.formString!=null) ? this.formString +"&" : "";
		pString += parameters.join('&');
		
		return pString;
	},
	validate: function( ) {
		return true;
	},
	processHeaders: function( ) {
		if(this.requestMethod == pimpas.HTTPRequest.methods.POST)
			this.headers.push(['Content-type', 'application/x-www-form-urlencoded']);
			
		this.headers.push(['X-Requested-With', 'XMLHttpRequest']);
		this.headers.push(['Pimpas-Framework-JS-Version', pimpas.version]);
	},
	request: function() {
		if(this.getUrl( ) == null)
			throw new UrlNotFoundException();
			
		// already running?!
		if(this.status != pimpas.HTTPRequest.events.unInitialized)
			// already running
			throw new RequestAlreadyRunning();
		
		if(this.transport == null) this.initTransport( );	
			
		if(!this.validate()) {
			this.log.info("[HTTPREQUEST] Error in validate! Returning...");	
			return;
		}
		
		// headers
		this.processHeaders( );
		
		var pString = this.processParameters( );
		
		if(this.requestMethod == pimpas.HTTPRequest.methods.GET && pString.length > 0)
			this.setUrl(this.getUrl() + (this.getUrl( ).match(/\?/) ? '&' : '?') + pString);
			
		if(this.async)
			this.transport.onreadystatechange = this.onReadyStateChange.bind(this);
		
		this.transport.open(this.getRequestMethod(), this.getUrl( ), this.async, this.userName, this.passWord);
		
		for(var i=0;i<this.headers.length;i++)
			this.transport.setRequestHeader(this.headers[i][0], this.headers[i][1]);

		try {
			pimpas.HTTPRequest.activeRequests++;
			this.log.log(pimpas.Log.DEBUG, 'Executing Request for Object ['+this.id+'] [URL]: '+this.getUrl()+", [PARAMETERS]: "+pString+", [ASYNC]: "+this.async);
			this.status = this.callListeners(pimpas.HTTPRequest.events.onInitialize);
			this.transport.send((this.getRequestMethod() == pimpas.HTTPRequest.methods.POST) ? pString : null);
			if(this.async)
				this.timer.go( );
			else
				this.onReadyStateChange( );
		} catch(e) {
			pimpas.HTTPRequest.activeRequests--;
			if(this.timer.isExecuting())
				this.timer.stop( );
			this.log.log(pimpas.Log.ERROR, e);
			if(pimpas.Browser.isIE)				
				this.transport.onreadystatechange = pimpas.emptyFunction;
			
			if(this.async)
				this.status = this.callListeners(pimpas.HTTPRequest.events.onError, e);
			else
				throw e;
		}
	},
	onReadyStateChange: function( ) {
		// is dead?!
		if(pimpas.HTTPRequest.isFinished(this.status)) {
			// is dead but transport still alive? WTF??
			// throw it away one more time and forever
			try {
				this.transport.abort( );
			} catch(e) {
				this.log.error(e.message);
				// dammit, we dont want u anymore, can't u understand????
			}
			return;
		}
		switch(this.transport.readyState) {
			case 1:
				this.status = this.callListeners(pimpas.HTTPRequest.events.onLoading);
			break;
			
			case 2:
				this.status = this.callListeners(pimpas.HTTPRequest.events.onLoaded);
			break;
			
			case 3:
				this.status = this.callListeners(pimpas.HTTPRequest.events.onInteractive);
				this.lastCall = new Date().getTime();
			break;
			
			case 4:
				pimpas.HTTPRequest.activeRequests--;
				this.timer.stop( );
				var success =false;
				if(pimpas.Browser.isIE)
					this.transport.onreadystatechange = pimpas.emptyFunction;
				if(this.transport.status == 0 || this.transport.status == 200) {
					var xml = this.transport.responseXML;
					success = true;
					var obbj = this.transport.responseText;
					if(xml != null) {
						if (!xml.nodeType || pimpas.Browser.isIE)
							xml = pimpas.HTTPRequest.fixXML( this.transport.responseText );
						
						var xmlObject = new pimpas.XML(xml);
						obbj = xmlObject;
					}
					
					this.status = this.callListeners(pimpas.HTTPRequest.events.onComplete, this.transport.responseText, obbj, xml, this.transport);
					this.log.log(pimpas.Log.INFO, 'Object ['+this.id+'] FINISHED REQUEST!');
				} else {
					this.log.log(pimpas.Log.ERROR, 'Unrecognized Error in Object '+this.id+', ERROR: '+this.transport.statusText);
					this.status = this.callListeners(pimpas.HTTPRequest.events.onError, this.transport.statusText);
				}
			break;
		}
	},
	abort: function() {
		if(!pimpas.HTTPRequest.isFinished(this.status)) {
			pimpas.HTTPRequest.activeRequests--;
			if(this.timer.isExecuting())
				this.timer.stop( );
			this.status = this.callListeners(pimpas.HTTPRequest.events.onAbort);
			try {
				this.transport.abort( );
			} catch(e) {
				this.log.error(e.message);
			}
			this.log.info('Request Object ['+this.id+'] aborted!');
		}
	},
	toString: function() {
		return "[PIMPAS HTTPREQUEST OBJECT]";
	}
}

);

pimpas.Extends(pimpas.HTTPRequest, {
   methods: {
		GET: "get",
		POST: "post"
	},
	activeRequests: 0,
	createTransport: function( ) {
		var obj = null;
		if(pimpas.Browser.isMozilla)
			obj = obj = new XMLHttpRequest();
		else {
			try {
				obj = new ActiveXObject('Microsoft.XMLHTTP');
			} catch(e) {
				try {
					obj = new ActiveXObject("MSXML2.DOMDocument");
				} catch(e1) {
					try {
						obj = new XMLHttpRequest();
					} catch(e2) {
						// well, is not my fault ur dummy stupid fucked browser!	
						// eat my exception
						throw e2;
					}
				}
			}
		}
		return obj;
	},
	fixXML: function( xml ) {
		var s = "" + xml;
		if (window.ActiveXObject) {
			xml = new ActiveXObject("MSXML2.DOMDocument");
			xml.loadXML(s);
		} else if(document.implementation)  {
			var parser = new DOMParser();
			xml = parser.parseFromString(s,"text/xml");    
		}
		
		return xml;
	},
	bypassMozilla: function() {
		if(pimpas.Browser.isMozilla)
			try {
				netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
			} catch (e) {
			}
	},
	events: {
		unInitialized: "Uninitialized",
		onCreate: "onCreate",
		onInitialize: "onInitialize",
		onLoading: "onLoading",
		onLoaded: "onLoaded",
		onInteractive: "onInteractive",
		onComplete: "onComplete",
		onError: "onError",
		onAbort: "onAbort",
		onResponse: "onResponse"
	},
	getInstance: function() {
		return HTTPRequestPool.get();
	},
	requests: new Array(),
	defaultTime: 60000,
	abortAll: function( ) {
		while(this.requests.length>0) {
			try {
				this.requests.pop().abort( );
			} catch(e) {
			}
		}
	},
	globalListeners: new Array(),
	addGlobalListener: function(t) {
		this.globalListeners.push(t);
	},
	isFinished: function(status) {
		return (status == pimpas.HTTPRequest.events.onComplete || status == pimpas.HTTPRequest.events.onError || status == pimpas.HTTPRequest.events.onAbort);
	}
}
);

pimpas.HTTPRequest.Pool = Class.create( );
pimpas.Extends(pimpas.HTTPRequest.Pool.prototype, 		   			   
	{
		poolSize: 10,
		freeSlots: 0,
		initialized: false,
		isInitialized: function( ) {
			return this.initialized;
		},
		pool: new Array(),
		waitTime: 5000,
		getWaitTime: function( ) {
			return this.waitTime;
		},
		setWaitTime: function(t) {
			this.waitTime = t;
		},
		maxAttempts: 10,
		getMaxAttempts: function( ) {
			return this.maxAttempts;	
		},
		setMaxAttempts: function(t) {
			this.maxAttempts = t;
		},
		setPoolSize: function(t) {
			this.poolSize = t;
		},
		getPoolSize: function() {
			return this.poolSize;
		},
		get: function() {
			if(!this.isInitialized()) return null;
			
			if(this.freeSlots > 0) {
				var obj = this.pool.shift();
				this.freeSlots--;
				this.log.log(pimpas.Log.INFO, "[HTTPREQUEST POOL]: Getting HTTPREQUEST from Pool. Lefts: "+this.freeSlots);
				return obj;
			} else {
				if(arguments.length == 1) {
					var n = parseInt(arguments[0]);
					if(isNaN(n)) return this.get(0);
					n+=1;
					if(n > this.maxAttempts) throw new NoHttpRequestInPoolException("try again later");
					return this.get(n);
				}
			}
			return this.get(0);
		},
		put: function(obj) {
			if(!this.isInitialized()) return null;
			
			if(!(obj instanceof pimpas.HTTPRequest)) return;
			if(this.freeSlots == this.getPoolSize()) return;
			obj.cleanUp( );
			obj.setPooled( true );
			this.pool.push(obj);
			this.freeSlots++;
			this.log.log(pimpas.Log.INFO, "[HTTPREQUEST POOL]: Adding HTTPREQUEST in Pool. Lefts: "+this.freeSlots);
		},
		initialize: function() {
			this.id = pimpas.globalID++;
			this.log = new pimpas.Log();
		},
		init: function() {
			if(this.initialized) return;
			this.initialized = true;
			if(arguments.length > 0)
				if(!isNaN(arguments[0]))
					this.setPoolSize(parseInt(arguments[0]));
			for(var i=0;i<this.getPoolSize();i++)
				this.put(new pimpas.HTTPRequest());
				
			this.log.log(pimpas.Log.INFO, 'Pool ['+this.id+'] initialized!');
		}
	}
);
// exceptions
NoHttpRequestInPoolException = Class.create();
pimpas.Extends(NoHttpRequestInPoolException.prototype, pimpas.Object);
pimpas.Extends(NoHttpRequestInPoolException.prototype,
			   {
				   toString: function( ) {
						return "NoHttpRequestInPoolException";
				   }
			   }
);
UrlNotFoundException = Class.create();
pimpas.Extends(UrlNotFoundException.prototype, pimpas.Object);
pimpas.Extends(UrlNotFoundException.prototype,
			   {
				   toString: function( ) {
						return "UrlNotFoundException";
				   }
			   }
);
RequestAlreadyRunning = Class.create();
pimpas.Extends(RequestAlreadyRunning.prototype, pimpas.Object);
pimpas.Extends(RequestAlreadyRunning.prototype,
			   {
				   toString: function( ) {
						return "RequestAlreadyRunning";
				   }
			   }
);

pimpas.Extends(pimpas.HTTPRequest.Pool.prototype, pimpas.Object);
var HTTPRequestPool = new pimpas.HTTPRequest.Pool();

pimpas.HTTPRequest.Queue = Class.create( );
pimpas.Extends(pimpas.HTTPRequest.Queue.prototype,
	{
		queue: null,
		timer: null,
		executing: null,
		listener: null,
		handleQueue: function() {
			if(this.executing != null) return;
			if(this.queue.length > 0) {
				this.executing = this.queue.shift();
				this.log.info("[Starting request from pool]");
				this.executing.request( );
			}
		},
		callBack: function(object, evt) {
			if(evt == pimpas.HTTPRequest.events.onComplete ||
			   evt == pimpas.HTTPRequest.events.onAbort ||
			   evt == pimpas.HTTPRequest.events.onError) {
			    this.log.info("[Request Finished!]");
				
			    new pimpas.Timer(100, function(){
					if(this.executing == null) {
						this.handleQueue( );
						return;
					}					
					if(this.executing.isPooled()) {
						this.log.info("[Returning Request to Pool!]");
						HTTPRequestPool.put( this.executing );
					} else
						this.executing.cleanUp( );
					this.executing = null;
					this.handleQueue( );
				}, this).go( );
			}
		},
		initialize: function() {
			this.queue = new Array();
			this.timer = new pimpas.Interval(300, this.handleQueue, this);
			this.id = pimpas.globalID++;
			this.log = new pimpas.Log();
			var temp = this;
			this.listener = new pimpas.HTTPRequest.Listener();
			pimpas.Extends(this.listener, {
								callBack: function(obj, evt) {
									temp.callBack(obj, evt);
								}
						   }
			);
			this.timer.go();
		},
		add: function(req) {
			if(!(req instanceof pimpas.HTTPRequest)) {
				this.log.log(pimpas.Log.ERROR, "Object ["+req+"] its not type of pimpas.HTTPRequest.");
				return;
			}
			
			req.addListener(this.listener);
			req.queueMode = true;
			this.queue.push(req);
			//this.handleQueue( );
		},
		remove: function(req) {
			
		},
		current: function() {
			return this.executing;
		}
	}
);
var HTTPRequestQueue = new pimpas.HTTPRequest.Queue();